#!/usr/local/bin/lua

-- 函数只有一个参数,且参数是字符串常量或者表构造器时,可以不使用圆括号

-- 等价于print("Hello World")
print "Hello World"
 
-- 等价于dofile('a.lua')
-- dofile 'a.lua'
 
-- 等价于print([[a multi-line
-- message]])
print [[a multi-line
    message]]

function f(t)
    for k,v in pairs(t) do
        print(k,v)
    end
end

-- 等价于f({x = 10, y = 20})
f{x = 10, y = 20}
 
-- 等价于type({})
print(type{})

-- 调用参数个数与定义参数个数不一致
-- 调用函数时传递的参数个数可以与定义函数时定义的参数个数不一致,Lua会通过抛弃多余参数和将不足的参数设为nil的方式来调整参数的格式
function f(a, b) 
    print(a, b) 
end

-- 等价于f(nil, nil)
f()
-- 等价于f(3, nil)
f(3)
-- 等价于f(3, 4)
f(3, 4)
-- 等价于f(3, 4), 抛弃5
f(3, 4, 5)

-- 这种行为可能导致编程错误(在单元测试中容易发现),但同样又是有用的,尤其是对于默认参数的情况。例如:
-- 该函数以1作为默认实参
-- 当调用无参数的inCount()时,将globalCounter+1；当调用有参数的inCount(n)时,会把globalCounter+n
globalCounter = 10

function inCount(n)
    n = n or 1
    globalCounter = globalCounter + n
end

inCount()
print(globalCounter)
inCount(nil)
print(globalCounter)
inCount(10)
print(globalCounter)

-- 不返回结果
function foo0() 
end
-- 返回1个结果
function foo1() 
    return "a" 
end
 
-- 返回2个结果
function foo2() 
    return "a", "b" 
end
-- 如果一个函数调用是一系列表达式中的最后(或者唯一)一个表达式,则该函数调用将产生尽可能多的返回值以匹配待赋值变量
-- x="a", y="b"
x, y = foo2()
 
-- x="a", 返回值"b"被丢弃
x = foo2()
 
-- x=10, y="a", z="b"
x, y, z = 10, foo2()

-- 果一个函数没有返回值或者返回值个数不够多,那么Lua会用nil来补充缺失的值
-- x=nil, y=nil
x, y = foo0()
-- x="a", y=nil
x, y = foo1()
-- x="a", y="b", z=nil
x, y, z = foo2()

-- 有当函数调用是一系列表达式中的最后(或者是唯一)一个表达式时才能返回多值结果,否则只能返回一个结果。
-- x="a", y=20 ,其中foo2()的返回值"b"被丢弃了
x, y = foo2(), 20
 
-- x=nil, y=20, 最后的30被丢弃了
x, y = foo1(), 20, 30

-- 当函数作为其他函数的参数传递时,有如下的规则:
-- 当一个函数调用是另一个函数调用的最后一个(或者唯一)实参时,函数的所有返回值都会被作为实参传给第二个函数(见下面演示案例1)
-- 当一个函数调用是另一个函数调用的实参时,但是如果这个函数调用后面还有参数,那么这个函数只返回第一个返回值给第二个函数
print("----------------")
print(foo0())
print(1, foo0())
 
print(foo1())
print(2, foo1())

print(foo2())
print(3, foo2())

print("----------------")
print(foo2())
print(foo2(), 3)
print(foo2() .. "x")

-- 表构造器
-- 当函数调用被用在表构造器中时,有如下的规则:
-- 当这个函数是表构造器中的最后一个(或者唯一)实参时,函数的所有返回值都会返回给表构造器(见下面演示案例1)
-- 当这个函数用来表构造器中,但是函数后面还有元素时,那么函数只返回第一个返回值
-- t1 = {}
t1 = {foo0()}
-- t2 = {"a"}
t2 = {foo1()}
-- t3 = {"a", "b"}
t3 = {foo2()}

-- t1 = {"a", 4}
t1 = {foo2(), 4}
-- t2 = {nil, "a", 4}
t2 = {foo0(), foo2(), 4}

-- return语句
-- 函数可以作为其他函数的return返回值进行返回,原理与上面的都是相同的:
-- 当这个函数作为其他函数return的返回值返回时,如果是return的最后一个(或者唯一)实参时,函数的所有返回值都会返回
-- 当这个函数作为其他函数return的返回值返回时,如果其后面还有别的返回值,那么只返回其第一个返回值
function foo(i)
    if i == 0 then return foo0()
    elseif i == 1 then return foo1()
    elseif i == 2 then return foo2()
    elseif i == 3 then return foo2(), 10
    elseif i == 4 then return 10,20,foo2()
    end
end
 
print("-----------------")
print(foo(0))
print(foo(1))
print(foo(2))
print(foo(3))
print(foo(4))

-- 强制返回一个返回值
-- x="a", y="b"
x, y = foo2()
print(x, y)

-- x="a", y=nil
x, y = (foo2())
print(x, y)

function foo(i)
    if i == 1 then return foo2()
    elseif i ==2 then return (foo2())
    end
end

print("-----------------")
print(foo(1))
 
-- 下面都只返回1个值
print(foo(2))
print((foo(1)))

-- 可变长参数
function add(...)
    local s = 0
    for _, v in ipairs{...} do
        s = s + v
    end
    return s
end

print("-----------------")
print(add(1))
print(add(1, 2, 3))
print(add(3, 4, 10, 25, 12))


function fwrite(fmt, ...)
    return io.write(string.format(fmt, ...))
end
print("-----------------")

fwrite("Hello %d %s\n",100,"World")
print("-----------------")


-- 可变长参数的遍历方式1(表遍历)
-- 要遍历可变长参数,可以使用表达式{...}将可变长参数放在一个表中
function foo(...)
    a = {...}
    for index = 1, #a do
        print(a[index])
    end
end
 
foo("a", "b", 10)
foo("a", "b", {"HelloWorld", 666}, 10)
print("-----------------")

-- 不过,如果可变长参数中包含无效的nil,那么{...}获得的表可能不再是一个有效的序列。此时,就没有办法在表中判断原始参数究竟是不是以nil结尾的

-- 可变长参数的遍历方式2(table.pack()函数)
-- 在Lua 5.2中引入了table.pack()函数,该函数像表达式{...}一样保存了所有参数,然后将其放在一个表中返回,但是这个表还有一个保存了参数个数的额外字段"n"
function nonils(...)
    local arg = table.pack(...)
    for i = 1, arg.n do
        if arg[i] == nil then 
            return false 
        end
    end
    return true
end
 
print(nonils(2, 3, nil))
print(nonils(2, 3))
print(nonils())
print(nonils(nil))
print("-----------------")


-- 可变长参数的遍历方式3(select()函数)
-- 另一种遍历可变长参数的方式是使用select()函数
-- select()函数的参数1决定了该函数的行为:
-- 如果参数1是一个数值n,则select()返回第n个参数后的所有参数
-- 如果参数1是#,则select()返回参数1后面所有参数的总数
print(select(1, "a", "b", "c"))
print(select(2, "a", "b", "c"))
print(select(3, "a", "b", "c"))
print(select("#", "a", "b", "c"))
print("-----------------")


function add(...)
    local s = 0
    for i = 1, select('#', ...) do
        -- 由于 s = s + select(i, ...)中的+运算符只能处理单个数值,Lua 会自动取 select(i, ...) 的第一个返回值(即第 i 个参数的值)来进行加法运算
        s = s + select(i, ...)
    end
    return s
end

print(add(1))
print(add(1, 2, 3))
print(add(3, 4, 10, 25, 12))
print("-----------------")

-- able.unpack()
-- 多重返回值还涉及一个特殊的函数table.unpack(),该函数的参数是一个数组,返回值为数组内的所有元素
print(table.unpack{10, 20, 30})
a, b = table.unpack{10, 20, 30}
print(a, b)

-- table.pack():把参数列表转换为Lua语言中的一个列表(表)
-- table.unpack():把列表(表)转换为一组值,进而可以作为另一个函数的参数被使用

-- table.unpack()使用长度操作符#来获取返回值的个数,因此该函数只能用于序列
-- 我们还可以使用圆括号限制返回元素的范围
print(table.unpack({"Sun", "Mon", "Tue", "Wed"}, 2, 3))
print("-----------------")


function unpack(t, i, n)
    i = i or 1
    n = n or #t
    if i <= n then
        return t[i], unpack(t, i + 1, n)
    end
end
 
print(unpack{10, 20, 30})